package com.atguigu.security.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.sql.DataSource;
import java.io.IOException;

/**
 * SpringSecurity配置类
 *
 * @author wmb
 *
 * @since 2021-1-27
 */
// 注意！这个类一定要放在自动扫描包下，否则所有配置都不会生效！
// 将当前类标记为配置类
@Configuration
// 启用Web环境下权限控制的功能
@EnableWebSecurity
@SuppressWarnings("all")
public class WebAppSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private DataSource dataSource;

    @Autowired
    private MyUserDetailsService userDetailsService;

//    @Autowired
//    private MyPasswordEncoder passwordEncoder;

    @Autowired
    private BCryptPasswordEncoder passwordEncoder;

    // 每次调用这个方法时会检查IOC容器中是否有了对应的bean，如果有就不会真正执行这个函数，因为bean默认是单例的
    // 可以使用@Scope(value = "")进行控制
    @Bean
    public BCryptPasswordEncoder getBCryptPasswordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder builder) throws Exception {
//        builder
//                .inMemoryAuthentication()   // 在内存中完成账号、密码的检查
//                .passwordEncoder(new BCryptPasswordEncoder())
//                .withUser("tom")    // 指定账号
//                .password(new BCryptPasswordEncoder().encode("123123"))         // 指定密码
//                .roles("ADMIN", "学徒")             // 指定当前用户的权限
//                .and()
//                .withUser("jerry")  // 指定账号
//                .password(new BCryptPasswordEncoder().encode("123123"))         // 指定密码
//                .authorities("UPDATE", "内门弟子")            // 指定当前用户的权限
//                ;

        // 装配userDetailsService对象
        builder
                .userDetailsService(userDetailsService)
                .passwordEncoder(passwordEncoder);
    }

    @Override
    protected void configure(HttpSecurity security) throws Exception {

        JdbcTokenRepositoryImpl tokenRepository = new JdbcTokenRepositoryImpl();
        tokenRepository.setDataSource(dataSource);

        security
                .authorizeRequests()                    // 对请求进行授权
                .antMatchers("/index.jsp", "/layui/**")  // 针对/index.jsp路径和layui目录下所有资源进行授权进行授权
                .permitAll()                            // 可以无条件访问
                .antMatchers("/level1/**")  // 针对/level1/**路径设置访问要求
                .hasRole("学徒")                          // 要求用户具备“学徒”的角色才可以访问
                .antMatchers("/level2/**")  // 针对/level2/**路径设置访问要求
                .hasAuthority("内门弟子")               // 要求用户具备“内门弟子”的权限才可以访问
                .and()
                .authorizeRequests()                    // 对请求进行授权
                .anyRequest()                           // 任意请求
                .authenticated()                       // 需要登录以后才可以访问
                .and()
                .formLogin()                            // 使用表单形式登录
                // 关于loginPage()方法的特殊说明
                // 指定登录页的同时会影响到："提交登录表单的地址"、"退出登录地址"、"登录失败地址"
                // /index.jsp GET   - the login form
                // /index.jsp POST  - process the credentials and if valid authenticate the user
                // /index.jsp?error GET - redirect here for failed authentication attempts
                // /index.jsp?logout GET - redirect here after successful logging out
                .loginPage("/index.jsp")                // 指定登录页面（如果没有指定会访问SpringSecurity自带的登陆页面）
                // loginProcessingUrl()方法指定了登录地址，就会覆盖loginPage()方法中设置的默认值
                .loginProcessingUrl("/do/login.html")   // 指定提交登录表单的地址/index.jsp POST
                .permitAll()                            // 登录地址本身也需要permitAll()放行
                .usernameParameter("loginAccount")      // 定制登录账号的请求参数名
                .passwordParameter("password")          // 定制登录密码的请求参数名
                .defaultSuccessUrl("/main.html")        // 登录成功前往的地址
                .and()
//                .csrf()
//                .disable()                              // 禁用CSRF功能
                .logout()                               // 开启退出功能
                .logoutUrl("/do/logout.html")           // 指定处理退出请求的URL地址
                .logoutSuccessUrl("/index.jsp")         // 退出成功后前往的地址
                .and()
                .exceptionHandling()                    // 指定异常处理器
//                .accessDeniedPage("/to/no/auth/page.html")  // 访问被拒绝时前往的页面
                .accessDeniedHandler(new AccessDeniedHandler() {
                    @Override
                    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException e) throws IOException, ServletException {
                        request.setAttribute("message", "抱歉！您无法访问这个资源！☆☆☆");
                        request.getRequestDispatcher("/WEB-INF/views/no_auth.jsp").forward(request, response);
                    }
                })
                .and()
                .rememberMe()                           // 记住我
                .tokenRepository(tokenRepository)
                ;
    }
}
